<?php
/* 
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 * Description of RQLPath
 *
 * @author Admin
 */
class RQLPath {
    protected $nodes = array();
    protected $relations = array();
    protected $from = array();
    protected $where = array();
    protected $insert = array();
    protected $select = array();
    protected $update = array();
    protected $delete = array();
    protected $salt;
    protected $last_insertion = 0;
    protected $last_deletion = 0;

    /**
     *
     * @return RQLNode
     */
    function get_parent_node() {
        $node = array_shift($this->nodes);
        var_dump($this->nodes);
        array_unshift($this->nodes, $node);
        return $node;
    }

    function get_last_outcome() {
        $node = array_pop($this->nodes);
        array_push($this->nodes, $node);
        return $node;
    }

    function set_last_outcome($val) {
        $node = array_pop($this->nodes);
        array_push($this->nodes, $val);
        return $node;
    }

    function push_node($node) {
        array_push($this->nodes, $node);
    }

    function unshift($node) {
        array_unshift($this->nodes, $node);
    }

    public function  __construct( $first = null) {
        $this->salt = microtime().rand(0, 10000);
        if (is_array($first)) {
            $this->nodes = $first;
        } elseif ($first !== null ) $this->add_node($first);
    }

    public function add_node( $node) {

        $i = count($this->nodes);
        $this->nodes[$i] = $node;

    }

    public function add_fork($fork) {
        $this->add_node($fork);
    }

    public function get_insert_queries() {
        $queries = array();
        while(($insert_name = $this->get_next_insertion()) !== false) {
            $skip = 0;
            while(($path = $this->get_node_path($insert_name, $skip++, 'insert')) !== false) {
                $path = new RQLPath($path);
                $insertion_node = $path->drop_last_node();
                $path->drop_operations_data();
                $parent_node = $path->get_last_outcome();
//                $parent_node->setOperation('$');
//                $parent_node->add_selection_field('id');
                $pack = null;
//                $path->set_last_outcome($parent_node);
                $check = $path->get_insert_query(RQL::ACL_SELECT | RQL::ACL_CHILD_APPEND, $pack);
                $queries[] = array(
                    'check'     => $check,
                    'insert'    => $insertion_node->name
                );
            }
        }
        return $queries;
    }

    public function get_deletion_queries() {
        $queries = array();
        while(($delete_name = $this->get_next_deletion()) !== false) {
            $skip = 0;
            while(($path = $this->get_node_path($delete_name, $skip++)) !== false) {
                $path = new RQLPath($path);
                $path->drop_operations_data();
                $path->drop_selection_data();
                $deletion_node = $path->get_last_outcome();
                $path->set_last_outcome($deletion_node);
                $ids_query = $path->get_select_query(RQL::ACL_SELECT | RQL::ACL_CHILD_OPERATION);
                $queries[] = $ids_query;
            }
        }
        return $queries;
    }


    public function drop_operations_data() {
        for($i=0;$i<count($this->nodes);$i++) {
            $this->nodes[$i]->drop_operations_data();
        }
    }

    public function drop_selection_data() {
        for($i=0;$i<count($this->nodes);$i++) {
            $this->nodes[$i]->drop_selection_data();
        }
    }

    protected function get_insertion($name) {
        $query = array();
//        for($i=0; $)
    }

    public function get_select_array($acl = RQL::ACL_SELECT, $packed = null) {
        $query = $this->get_path_to(count($this->nodes), $acl, $packed);
        return $query;
    }

    public function get_select_query($acl = RQL::ACL_SELECT, $packed = null) {
        $query = $this->get_path_to(count($this->nodes), $acl, 'select');
        if (trim($query['select']) == '') {
            $last = $this->get_last_node();
            $query['select'] = $last->rid.'.id';
        }

        return "SELECT DISTINCT {$query['select']} FROM {$query['from']} WHERE {$query['where']}";
    }

    public function get_delete_query($acl = RQL::ACL_DELETE, $packed = null) {
        $query = $this->get_path_to(count($this->nodes), $acl, 'delete');
        if (trim($query['select']) == '') {
            return '';
        }

        return "SELECT DISTINCT {$query['select']} FROM {$query['from']} WHERE {$query['where']}";
    }

    public function get_insert_query($acl = RQL::ACL_SELECT, $packed = null) {
        $query = $this->get_select_array($acl, $packed);
        $node = $this->get_last_node();
        if (!is_a($node, 'RQLFork')) $node = @$this->nodes[count($this->nodes)-2];
        if (!$node) {
            $node = $this->get_first_node();
        }
        $query['select'] = $node->rid.'.child';
        $query['select'] .= ', '.$node->rid.'.child_id';

        return "SELECT DISTINCT {$query['select']} FROM {$query['from']} WHERE {$query['where']}";
    }

    public function get_branch_query($acl = RQL::ACL_SELECT, $mode = '||') {
        $query = $this->get_select_array($acl);
        $node = @$this->nodes[count($this->nodes) - 2];
        if (!$node) {
            $node = $this->get_first_node();
        }

        if ($mode == '&&') $query['select'] = $node->rid.'.*';
        else $query['select'] = $node->rid.'.id';
        return "SELECT DISTINCT {$query['select']} FROM {$query['from']} WHERE {$query['where']}";
    }

    protected function get_path_to($position, $acl, $operation = 'select') {
        $pnode = null;
        $query = array(
            'select' => array(),
            'from' => array(),
            'where'=>array(),
            'update'=>array()
        );
        if ($position > count($this->nodes))
                throw new Exception('Position '.$position.' can`t be greater than '.(count($this->nodes)));
        for($i=0;$i<$position;$i++) {
            $from = '';
            $where = '';
            $update = '';
            $node = $this->nodes[$i];
            if ((false != ($select_fields = $node->get_selected_fields($pnode, $operation)))) {
                $query['select'] = array_merge($query['select'], $select_fields);
            }
            $from = $node->getFrom($pnode, $operation);
            $where = $node->getWhere($pnode, $acl, @$this->nodes[$i+1]);
            $update = $node->getUpdate();
            if ($from) $query['from'] = array_merge($query['from'], $from);
            if ($where) $query['where'][] = $where;
            if ($update) $query['update'][] = $update;
            $pnode = $node;
        }

        $query['from'] = array_unique($query['from']);
        $query['select'] = array_unique($query['select']);
        if (count($query['select'])==0) $query['select'] = $this->nodes[$position-1]->rid.'.id';
        $query['select'] = @implode(', ', $query['select']);
        $query['from'] = implode(', ', $query['from']);
        $query['update'] = implode(', ', $query['update']);
        $query['where'] = implode(' AND ', $query['where']);
        return $query;
    }

    /**
     *
     * @return RQLNode
     */
    public function get_last_node() {
        $i = count($this->nodes) - 1;
        $node = $this->nodes[$i];
        return $node;
    }

    public function get_first_node() {
        $node = $this->nodes[0];
        return $node;
    }

    public function set_last_node($node) {
        $i = count($this->nodes) -1;
        $this->nodes[$i] = $node;
        return $this;
    }

    public function getWhere($parent = null,$acl = QConst::RQL_SELECT, $operation = null) {
        $w = array();
        foreach($this->nodes as $node) {
            $w[] = $node->getWhere($parent, $acl);
        }

        return implode(' AND ',$w);
    }

     public function getFrom($parent = null, $acl = QConst::RQL_SELECT) {
        $f = array();
        for($i=0;$i<count($this->nodes);$i++) {
            $node = $this->nodes[$i];
            $from = $node->getFrom($parent);
            if ($from) $f = array_merge($f, $from);
            $parent = $node;
        }

        return $f;
    }

    public function getSelect($parent = null) {
        $s = array();

        foreach($this->nodes as $node) {
            $s[] = $node->getSelect($parent);
            $parent = $node;
        }

        return implode(', ',$s);
    }

    public function getUpdate() {
        $res = array();
        for($i=0;$i<count($this->nodes);$i++) {
            $update = $this->nodes[$i]->getUpdate();
            if (count($update) > 0) {
                if (is_a($this->nodes[$i], 'RQLNode')) {
                    $res[$this->nodes[$i]->getName()] = $update;
                } else {
                    $res = array_merge($res, $update);
                }
            }
        }
        return $res;
    }

    public function get_real_name($alias) {
        for($i=0;$i<count($this->nodes); $i++) {
            $rn = $this->nodes[$i]->get_real_name($alias);
            if ($rn !== false) return $rn;
        }
    }

    public function get_next_insertion() {
        for($this->last_insertion; $this->last_insertion < count($this->nodes);$this->last_insertion++) {
            $outcome = $this->nodes[$this->last_insertion];
            if (is_a($outcome, 'RQLNode') && $outcome->must_insert()) {
                $this->last_insertion++;
                return $outcome->getName();
            } elseif(!is_a($outcome, 'RQLNode')) {
                $sub_insertion = $outcome->get_next_insertion();
                if ($sub_insertion !== false) {
                    $this->last_insertion++;
                    return $sub_insertion;
                }
            }
        }
        return false;
    }

    public function get_next_deletion() {
        for($this->last_deletion; $this->last_deletion < count($this->nodes);$this->last_deletion++) {
            $outcome = $this->nodes[$this->last_deletion];
            if (is_a($outcome, 'RQLNode') && $outcome->must_delete()) {
                $this->last_deletion++;
                return $outcome->getName();
            } elseif(!is_a($outcome, 'RQLNode')) {
                $sub_deletion = $outcome->get_next_deletion();
                if ($sub_deletion !== false) {
                    $this->last_deletion++;
                    return $sub_deletion;
                }
            }
        }
        return false;
    }

    /**
     *
     * @param string $name
     * @return array
     */
    public function get_node_path($name, $skip = 0) {
        $path = array();
        for($i=0;$i<count($this->nodes);$i++) {
            $node = $this->nodes[$i];
            if (is_a($node, 'RQLNode')) {
                $path[] = $node;
                if ($node->getName() == $name && $skip==0) {
                    return $path;
                } elseif($node->getName() == $name) $skip--;
            } else {
                $sub_path = $node->get_node_path($name);
                if ($sub_path !== false && is_array($sub_path) && $skip==0) {
                    $path = array_merge($path, $sub_path);
                    return $path;
                } elseif($sub_path !== false && is_array($sub_path)) $skip--;
            }
        }
        return false;
    }


    public function drop_last_node() {
        return array_pop($this->nodes);
    }

    public function get_update_query($acl = RQL::ACL_UPDATE) {
        $query = $this->get_path_to(count($this->nodes), $acl, 'update');
        if ($query['update'] == '') return '';
        return "UPDATE {$query['from']} SET {$query['update']} WHERE {$query['where']}";
    }

    public function get_selected_fields($parent = null) {
        $ret = array();
        for($i=0;$i<count($this->nodes);$i++) {
            $node = $this->nodes[$i];
            $fields = $node->get_selected_fields($parent);
            if (count($fields) > 0) $ret = array_merge($ret, $fields);
            $parent = $node;
        }
        return $ret;
    }

    public function get_selected_real_names($parent = null) {
        $ret = array();
        for($i=0;$i<count($this->nodes);$i++) {
            $node = $this->nodes[$i];
            $fields = $node->get_selected_real_names($parent);
            if (!$fields) continue;
            if (!is_array($fields)) $ret[] = $fields;
            elseif (count($fields) > 0) $ret = array_merge($ret, $fields);
            $parent = $node;
        }
        return $ret;
    }

    public function getName() {
        $last = $this->get_last_node();
        return $last->getName();
    }

    public function update_description_by_real_name($name, $field, $operator, $value) {
        for($i=0;$i<count($this->nodes);$i++) {
            if ($this->nodes[$i]->update_description_by_real_name($name, $field, $operator, $value)) {
                return true;
            }
        }
        return false;
    }
    public function must_select() {
        for($i=0;$i<count($this->nodes);$i++) {
            if ($this->nodes[$i]->must_select()) return true;
        }
    }

    public function must_insert() {
        for($i=0;$i<count($this->nodes);$i++) {
            if ($this->nodes[$i]->must_insert()) return true;
        }
    }

    public function must_delete() {
        for($i=0;$i<count($this->nodes);$i++) {
            if ($this->nodes[$i]->must_delete()) return true;
        }
    }


}
?>